home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2009 October / maximum-cd-2009-10.iso / DiscContents / Firefox Setup 3.5.exe / nonlocalized / chrome / toolkit.jar / content / global / nsDragAndDrop.js < prev    next >
Encoding:
JavaScript  |  2009-06-24  |  20.5 KB  |  584 lines

  1. //@line 39 "e:\builds\moz2_slave\win32_build\build\toolkit\content\nsDragAndDrop.js"
  2.  
  3. /** 
  4.  *  nsTransferable - a wrapper for nsITransferable that simplifies
  5.  *                   javascript clipboard and drag&drop. for use in
  6.  *                   these situations you should use the nsClipboard
  7.  *                   and nsDragAndDrop wrappers for more convenience
  8.  **/ 
  9.  
  10. var nsTransferable = {
  11.   /**
  12.    * nsITransferable set (TransferData aTransferData) ;
  13.    *
  14.    * Creates a transferable with data for a list of supported types ("flavours")
  15.    * 
  16.    * @param TransferData aTransferData
  17.    *        a javascript object in the format described above 
  18.    **/ 
  19.   set: function (aTransferDataSet)
  20.     {
  21.       var trans = this.createTransferable();
  22.       for (var i = 0; i < aTransferDataSet.dataList.length; ++i) 
  23.         {
  24.           var currData = aTransferDataSet.dataList[i];
  25.           var currFlavour = currData.flavour.contentType;
  26.           trans.addDataFlavor(currFlavour);
  27.           var supports = null; // nsISupports data
  28.           var length = 0;
  29.           if (currData.flavour.dataIIDKey == "nsISupportsString")
  30.             {
  31.               supports = Components.classes["@mozilla.org/supports-string;1"]
  32.                                    .createInstance(Components.interfaces.nsISupportsString);
  33.  
  34.               supports.data = currData.supports;
  35.               length = supports.data.length;
  36.             }
  37.           else 
  38.             {
  39.               // non-string data.
  40.               supports = currData.supports;
  41.               length = 0; // kFlavorHasDataProvider
  42.             }
  43.           trans.setTransferData(currFlavour, supports, length * 2);
  44.         }
  45.       return trans;
  46.     },
  47.   
  48.   /**
  49.    * TransferData/TransferDataSet get (FlavourSet aFlavourSet, 
  50.    *                                   Function aRetrievalFunc, Boolean aAnyFlag) ;
  51.    *
  52.    * Retrieves data from the transferable provided in aRetrievalFunc, formatted
  53.    * for more convenient access.
  54.    *
  55.    * @param FlavourSet aFlavourSet
  56.    *        a FlavourSet object that contains a list of supported flavours.
  57.    * @param Function aRetrievalFunc
  58.    *        a reference to a function that returns a nsISupportsArray of nsITransferables
  59.    *        for each item from the specified source (clipboard/drag&drop etc)
  60.    * @param Boolean aAnyFlag
  61.    *        a flag specifying whether or not a specific flavour is requested. If false,
  62.    *        data of the type of the first flavour in the flavourlist parameter is returned,
  63.    *        otherwise the best flavour supported will be returned.
  64.    **/
  65.   get: function (aFlavourSet, aRetrievalFunc, aAnyFlag)
  66.     {
  67.       if (!aRetrievalFunc) 
  68.         throw "No data retrieval handler provided!";
  69.       
  70.       var supportsArray = aRetrievalFunc(aFlavourSet);
  71.       var dataArray = [];
  72.       var count = supportsArray.Count();
  73.       
  74.       // Iterate over the number of items returned from aRetrievalFunc. For
  75.       // clipboard operations, this is 1, for drag and drop (where multiple
  76.       // items may have been dragged) this could be >1.
  77.       for (var i = 0; i < count; i++)
  78.         {
  79.           var trans = supportsArray.GetElementAt(i);
  80.           if (!trans) continue;
  81.           trans = trans.QueryInterface(Components.interfaces.nsITransferable);
  82.             
  83.           var data = { };
  84.           var length = { };
  85.           
  86.           var currData = null;
  87.           if (aAnyFlag)
  88.             { 
  89.               var flavour = { };
  90.               trans.getAnyTransferData(flavour, data, length);
  91.               if (data && flavour)
  92.                 {
  93.                   var selectedFlavour = aFlavourSet.flavourTable[flavour.value];
  94.                   if (selectedFlavour) 
  95.                     dataArray[i] = FlavourToXfer(data.value, length.value, selectedFlavour);
  96.                 }
  97.             }
  98.           else
  99.             {
  100.               var firstFlavour = aFlavourSet.flavours[0];
  101.               trans.getTransferData(firstFlavour, data, length);
  102.               if (data && firstFlavour)
  103.                 dataArray[i] = FlavourToXfer(data.value, length.value, firstFlavour);
  104.             }
  105.         }
  106.       return new TransferDataSet(dataArray);
  107.     },
  108.  
  109.   /** 
  110.    * nsITransferable createTransferable (void) ;
  111.    *
  112.    * Creates and returns a transferable object.
  113.    **/    
  114.   createTransferable: function ()
  115.     {
  116.       const kXferableContractID = "@mozilla.org/widget/transferable;1";
  117.       const kXferableIID = Components.interfaces.nsITransferable;
  118.       return Components.classes[kXferableContractID].createInstance(kXferableIID);
  119.     }
  120. };  
  121.  
  122. /** 
  123.  * A FlavourSet is a simple type that represents a collection of Flavour objects.
  124.  * FlavourSet is constructed from an array of Flavours, and stores this list as
  125.  * an array and a hashtable. The rationale for the dual storage is as follows:
  126.  * 
  127.  * Array: Ordering is important when adding data flavours to a transferable. 
  128.  *        Flavours added first are deemed to be 'preferred' by the client. 
  129.  * Hash:  Convenient lookup of flavour data using the content type (MIME type)
  130.  *        of data as a key. 
  131.  */
  132. function FlavourSet(aFlavourList)
  133. {
  134.   this.flavours = aFlavourList || [];
  135.   this.flavourTable = { };
  136.  
  137.   this._XferID = "FlavourSet";
  138.   
  139.   for (var i = 0; i < this.flavours.length; ++i)
  140.     this.flavourTable[this.flavours[i].contentType] = this.flavours[i];
  141. }
  142.  
  143. FlavourSet.prototype = {
  144.   appendFlavour: function (aFlavour, aFlavourIIDKey)
  145.   {
  146.     var flavour = new Flavour (aFlavour, aFlavourIIDKey);
  147.     this.flavours.push(flavour);
  148.     this.flavourTable[flavour.contentType] = flavour;
  149.   }
  150. };
  151.  
  152. /** 
  153.  * A Flavour is a simple type that represents a data type that can be handled. 
  154.  * It takes a content type (MIME type) which is used when storing data on the
  155.  * system clipboard/drag and drop, and an IIDKey (string interface name
  156.  * which is used to QI data to an appropriate form. The default interface is
  157.  * assumed to be wide-string.
  158.  */ 
  159. function Flavour(aContentType, aDataIIDKey)
  160. {
  161.   this.contentType = aContentType;
  162.   this.dataIIDKey = aDataIIDKey || "nsISupportsString";
  163.  
  164.   this._XferID = "Flavour";
  165. }
  166.  
  167. function TransferDataBase() {}
  168. TransferDataBase.prototype = {
  169.   push: function (aItems)
  170.   {
  171.     this.dataList.push(aItems);
  172.   },
  173.  
  174.   get first ()
  175.   {
  176.     return "dataList" in this && this.dataList.length ? this.dataList[0] : null;
  177.   }
  178. };
  179.  
  180. /** 
  181.  * TransferDataSet is a list (array) of TransferData objects, which represents
  182.  * data dragged from one or more elements. 
  183.  */
  184. function TransferDataSet(aTransferDataList)
  185. {
  186.   this.dataList = aTransferDataList || [];
  187.  
  188.   this._XferID = "TransferDataSet";
  189. }
  190. TransferDataSet.prototype = TransferDataBase.prototype;
  191.  
  192. /** 
  193.  * TransferData is a list (array) of FlavourData for all the applicable content
  194.  * types associated with a drag from a single item. 
  195.  */
  196. function TransferData(aFlavourDataList)
  197. {
  198.   this.dataList = aFlavourDataList || [];
  199.  
  200.   this._XferID = "TransferData";
  201. }
  202. TransferData.prototype = {
  203.   __proto__: TransferDataBase.prototype,
  204.   
  205.   addDataForFlavour: function (aFlavourString, aData, aLength, aDataIIDKey)
  206.   {
  207.     this.dataList.push(new FlavourData(aData, aLength, 
  208.                        new Flavour(aFlavourString, aDataIIDKey)));
  209.   }
  210. };
  211.  
  212. /** 
  213.  * FlavourData is a type that represents data retrieved from the system 
  214.  * clipboard or drag and drop. It is constructed internally by the Transferable
  215.  * using the raw (nsISupports) data from the clipboard, the length of the data,
  216.  * and an object of type Flavour representing the type. Clients implementing
  217.  * IDragDropObserver receive an object of this type in their implementation of
  218.  * onDrop. They access the 'data' property to retrieve data, which is either data 
  219.  * QI'ed to a usable form, or unicode string. 
  220.  */
  221. function FlavourData(aData, aLength, aFlavour) 
  222. {
  223.   this.supports = aData;
  224.   this.contentLength = aLength;
  225.   this.flavour = aFlavour || null;
  226.   
  227.   this._XferID = "FlavourData";
  228. }
  229.  
  230. FlavourData.prototype = {
  231.   get data ()
  232.   {
  233.     if (this.flavour &&
  234.         this.flavour.dataIIDKey != "nsISupportsString")
  235.       return this.supports.QueryInterface(Components.interfaces[this.flavour.dataIIDKey]); 
  236.  
  237.     var supports = this.supports;
  238.     if (supports instanceof Components.interfaces.nsISupportsString)
  239.       return supports.data.substring(0, this.contentLength/2);
  240.      
  241.     return supports;
  242.   }
  243. }
  244.  
  245. /** 
  246.  * Create a TransferData object with a single FlavourData entry. Used when 
  247.  * unwrapping data of a specific flavour from the drag service. 
  248.  */
  249. function FlavourToXfer(aData, aLength, aFlavour) 
  250. {
  251.   return new TransferData([new FlavourData(aData, aLength, aFlavour)]);
  252. }
  253.  
  254. var transferUtils = {
  255.  
  256.   retrieveURLFromData: function (aData, flavour)
  257.   {
  258.     switch (flavour) {
  259.       case "text/unicode":
  260.       case "text/plain":
  261.       case "text/x-moz-text-internal":
  262.         return aData.replace(/^\s+|\s+$/g, "");
  263.       case "text/x-moz-url":
  264.         return ((aData instanceof Components.interfaces.nsISupportsString) ? aData.toString() : aData).split("\n")[0];
  265.       case "application/x-moz-file":
  266.         var ioService = Components.classes["@mozilla.org/network/io-service;1"]
  267.                                   .getService(Components.interfaces.nsIIOService);
  268.         var fileHandler = ioService.getProtocolHandler("file")
  269.                                    .QueryInterface(Components.interfaces.nsIFileProtocolHandler);
  270.         return fileHandler.getURLSpecFromFile(aData);
  271.     }
  272.     return null;                                                   
  273.   }
  274.  
  275. }
  276.  
  277. /**
  278.  * nsDragAndDrop - a convenience wrapper for nsTransferable, nsITransferable
  279.  *                 and nsIDragService/nsIDragSession. 
  280.  *
  281.  * Use: map the handler functions to the 'ondraggesture', 'ondragover' and
  282.  *   'ondragdrop' event handlers on your XML element, e.g.                   
  283.  *   <xmlelement ondraggesture="nsDragAndDrop.startDrag(event, observer);"   
  284.  *               ondragover="nsDragAndDrop.dragOver(event, observer);"      
  285.  *               ondragdrop="nsDragAndDrop.drop(event, observer);"/>         
  286.  *                                                                           
  287.  *   You need to create an observer js object with the following member      
  288.  *   functions:                                                              
  289.  *     Object onDragStart (event)        // called when drag initiated,      
  290.  *                                       // returns flavour list with data   
  291.  *                                       // to stuff into transferable      
  292.  *     void onDragOver (Object flavour)  // called when element is dragged   
  293.  *                                       // over, so that it can perform     
  294.  *                                       // any drag-over feedback for provided
  295.  *                                       // flavour                          
  296.  *     void onDrop (Object data)         // formatted data object dropped.   
  297.  *     Object getSupportedFlavours ()    // returns a flavour list so that   
  298.  *                                       // nsTransferable can determine
  299.  *                                       // whether or not to accept drop. 
  300.  **/   
  301.  
  302. var nsDragAndDrop = {
  303.   
  304.   _mDS: null,
  305.   get mDragService()
  306.     {
  307.       if (!this._mDS) 
  308.         {
  309.           const kDSContractID = "@mozilla.org/widget/dragservice;1";
  310.           const kDSIID = Components.interfaces.nsIDragService;
  311.           this._mDS = Components.classes[kDSContractID].getService(kDSIID);
  312.         }
  313.       return this._mDS;
  314.     },
  315.  
  316.   /**
  317.    * void startDrag (DOMEvent aEvent, Object aDragDropObserver) ;
  318.    *
  319.    * called when a drag on an element is started.
  320.    *
  321.    * @param DOMEvent aEvent
  322.    *        the DOM event fired by the drag init
  323.    * @param Object aDragDropObserver
  324.    *        javascript object of format described above that specifies
  325.    *        the way in which the element responds to drag events.
  326.    **/  
  327.   startDrag: function (aEvent, aDragDropObserver)
  328.     {
  329.       if (!("onDragStart" in aDragDropObserver))
  330.         return;
  331.  
  332.       const kDSIID = Components.interfaces.nsIDragService;
  333.       var dragAction = { action: kDSIID.DRAGDROP_ACTION_COPY + kDSIID.DRAGDROP_ACTION_MOVE + kDSIID.DRAGDROP_ACTION_LINK };
  334.  
  335.       var transferData = { data: null };
  336.       try 
  337.         {
  338.           aDragDropObserver.onDragStart(aEvent, transferData, dragAction);
  339.         }
  340.       catch (e) 
  341.         {
  342.           return;  // not a draggable item, bail!
  343.         }
  344.  
  345.       if (!transferData.data) return;
  346.       transferData = transferData.data;
  347.  
  348.       var dt = aEvent.dataTransfer;
  349.       var count = 0;
  350.       do {
  351.         var tds = transferData._XferID == "TransferData" 
  352.                                          ? transferData 
  353.                                          : transferData.dataList[count]
  354.         for (var i = 0; i < tds.dataList.length; ++i) 
  355.         {
  356.           var currData = tds.dataList[i];
  357.           var currFlavour = currData.flavour.contentType;
  358.           var value = currData.supports;
  359.           if (value instanceof Components.interfaces.nsISupportsString)
  360.             value = value.toString();
  361.           dt.mozSetDataAt(currFlavour, value, count);
  362.         }
  363.  
  364.         count++;
  365.       }
  366.       while (transferData._XferID == "TransferDataSet" && 
  367.              count < transferData.dataList.length);
  368.  
  369.       dt.effectAllowed = "all";
  370.       // a drag targeted at a tree should instead use the treechildren so that
  371.       // the current selection is used as the drag feedback
  372.       dt.addElement(aEvent.originalTarget.localName == "treechildren" ?
  373.                     aEvent.originalTarget : aEvent.target);
  374.       aEvent.stopPropagation();
  375.     },
  376.  
  377.   /** 
  378.    * void dragOver (DOMEvent aEvent, Object aDragDropObserver) ;
  379.    *
  380.    * called when a drag passes over this element
  381.    *
  382.    * @param DOMEvent aEvent
  383.    *        the DOM event fired by passing over the element
  384.    * @param Object aDragDropObserver
  385.    *        javascript object of format described above that specifies
  386.    *        the way in which the element responds to drag events.
  387.    **/
  388.   dragOver: function (aEvent, aDragDropObserver)
  389.     { 
  390.       if (!("onDragOver" in aDragDropObserver)) 
  391.         return;
  392.       if (!this.checkCanDrop(aEvent, aDragDropObserver))
  393.         return;
  394.       var flavourSet = aDragDropObserver.getSupportedFlavours();
  395.       for (var flavour in flavourSet.flavourTable)
  396.         {
  397.           if (this.mDragSession.isDataFlavorSupported(flavour))
  398.             {
  399.               aDragDropObserver.onDragOver(aEvent, 
  400.                                            flavourSet.flavourTable[flavour], 
  401.                                            this.mDragSession);
  402.               aEvent.stopPropagation();
  403.               aEvent.preventDefault();
  404.               break;
  405.             }
  406.         }
  407.     },
  408.  
  409.   mDragSession: null,
  410.  
  411.   /** 
  412.    * void drop (DOMEvent aEvent, Object aDragDropObserver) ;
  413.    *
  414.    * called when the user drops on the element
  415.    *
  416.    * @param DOMEvent aEvent
  417.    *        the DOM event fired by the drop
  418.    * @param Object aDragDropObserver
  419.    *        javascript object of format described above that specifies
  420.    *        the way in which the element responds to drag events.
  421.    **/
  422.   drop: function (aEvent, aDragDropObserver)
  423.     {
  424.       if (!("onDrop" in aDragDropObserver))
  425.         return;
  426.       if (!this.checkCanDrop(aEvent, aDragDropObserver))
  427.         return;  
  428.  
  429.       var flavourSet = aDragDropObserver.getSupportedFlavours();
  430.  
  431.       var dt = aEvent.dataTransfer;
  432.       var dataArray = [];
  433.       var count = dt.mozItemCount;
  434.       for (var i = 0; i < count; ++i) {
  435.         var types = dt.mozTypesAt(i);
  436.         for (var j = 0; j < flavourSet.flavours.length; j++) {
  437.           var type = flavourSet.flavours[j].contentType;
  438.           // dataTransfer uses text/plain but older code used text/unicode, so
  439.           // switch this for compatibility
  440.           var modtype = (type == "text/unicode") ? "text/plain" : type;
  441.           if (Array.indexOf(types, modtype) >= 0) {
  442.             var data = dt.mozGetDataAt(modtype, i);
  443.             if (data) {
  444.               // Non-strings need some non-zero value used for their data length.
  445.               const kNonStringDataLength = 4;
  446.  
  447.               var length = (typeof data == "string") ? data.length : kNonStringDataLength;
  448.               dataArray[i] = FlavourToXfer(data, length, flavourSet.flavourTable[type]);
  449.               break;
  450.             }
  451.           }
  452.         }
  453.       }
  454.  
  455.       var transferData = new TransferDataSet(dataArray)
  456.  
  457.       // hand over to the client to respond to dropped data
  458.       var multiple = "canHandleMultipleItems" in aDragDropObserver && aDragDropObserver.canHandleMultipleItems;
  459.       var dropData = multiple ? transferData : transferData.first.first;
  460.       aDragDropObserver.onDrop(aEvent, dropData, this.mDragSession);
  461.       aEvent.stopPropagation();
  462.     },
  463.  
  464.   /** 
  465.    * void dragExit (DOMEvent aEvent, Object aDragDropObserver) ;
  466.    *
  467.    * called when a drag leaves this element
  468.    *
  469.    * @param DOMEvent aEvent
  470.    *        the DOM event fired by leaving the element
  471.    * @param Object aDragDropObserver
  472.    *        javascript object of format described above that specifies
  473.    *        the way in which the element responds to drag events.
  474.    **/
  475.   dragExit: function (aEvent, aDragDropObserver)
  476.     {
  477.       if (!this.checkCanDrop(aEvent, aDragDropObserver))
  478.         return;
  479.       if ("onDragExit" in aDragDropObserver)
  480.         aDragDropObserver.onDragExit(aEvent, this.mDragSession);
  481.     },  
  482.     
  483.   /** 
  484.    * void dragEnter (DOMEvent aEvent, Object aDragDropObserver) ;
  485.    *
  486.    * called when a drag enters in this element
  487.    *
  488.    * @param DOMEvent aEvent
  489.    *        the DOM event fired by entering in the element
  490.    * @param Object aDragDropObserver
  491.    *        javascript object of format described above that specifies
  492.    *        the way in which the element responds to drag events.
  493.    **/
  494.   dragEnter: function (aEvent, aDragDropObserver)
  495.     {
  496.       if (!this.checkCanDrop(aEvent, aDragDropObserver))
  497.         return;
  498.       if ("onDragEnter" in aDragDropObserver)
  499.         aDragDropObserver.onDragEnter(aEvent, this.mDragSession);
  500.     },  
  501.  
  502.   /** 
  503.    * Boolean checkCanDrop (DOMEvent aEvent, Object aDragDropObserver) ;
  504.    *
  505.    * Sets the canDrop attribute for the drag session.
  506.    * returns false if there is no current drag session.
  507.    *
  508.    * @param DOMEvent aEvent
  509.    *        the DOM event fired by the drop
  510.    * @param Object aDragDropObserver
  511.    *        javascript object of format described above that specifies
  512.    *        the way in which the element responds to drag events.
  513.    **/
  514.   checkCanDrop: function (aEvent, aDragDropObserver)
  515.     {
  516.       if (!this.mDragSession) 
  517.         this.mDragSession = this.mDragService.getCurrentSession();
  518.       if (!this.mDragSession) 
  519.         return false;
  520.       this.mDragSession.canDrop = this.mDragSession.sourceNode != aEvent.target;
  521.       if ("canDrop" in aDragDropObserver)
  522.         this.mDragSession.canDrop &= aDragDropObserver.canDrop(aEvent, this.mDragSession);
  523.       return true;
  524.     },
  525.  
  526.   /**
  527.    * Do a security check for drag n' drop. Make sure the source document
  528.    * can load the dragged link.
  529.    *
  530.    * @param DOMEvent aEvent
  531.    *        the DOM event fired by leaving the element
  532.    * @param Object aDragDropObserver
  533.    *        javascript object of format described above that specifies
  534.    *        the way in which the element responds to drag events.
  535.    * @param String aDraggedText
  536.    *        the text being dragged
  537.    **/
  538.   dragDropSecurityCheck: function (aEvent, aDragSession, aDraggedText)
  539.     {
  540.       var sourceDoc = aDragSession.sourceDocument;
  541.       if (!sourceDoc)
  542.         return;
  543.  
  544.       // Strip leading and trailing whitespace, then try to create a
  545.       // URI from the dropped string. If that succeeds, we're
  546.       // dropping a URI and we need to do a security check to make
  547.       // sure the source document can load the dropped URI. We don't
  548.       // so much care about creating the real URI here
  549.       // (i.e. encoding differences etc don't matter), we just want
  550.       // to know if aDraggedText really is a URI.
  551.  
  552.       aDraggedText = aDraggedText.replace(/^\s*|\s*$/g, '');
  553.  
  554.       var uri;
  555.  
  556.       try {
  557.         uri = Components.classes["@mozilla.org/network/io-service;1"]
  558.                         .getService(Components.interfaces.nsIIOService)
  559.                         .newURI(aDraggedText, null, null);
  560.       } catch (e) {
  561.       }
  562.  
  563.       if (!uri)
  564.         return;
  565.  
  566.       // aDraggedText is a URI, do the security check.
  567.       const nsIScriptSecurityManager = Components.interfaces
  568.                                                  .nsIScriptSecurityManager;
  569.       var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
  570.                              .getService(nsIScriptSecurityManager);
  571.  
  572.       try {
  573.         secMan.checkLoadURIStr(sourceDoc.documentURI, aDraggedText,
  574.                                nsIScriptSecurityManager.STANDARD);
  575.       } catch (e) {
  576.         // Stop event propagation right here.
  577.         aEvent.stopPropagation();
  578.  
  579.         throw "Drop of " + aDraggedText + " denied.";
  580.       }
  581.     }
  582. };
  583.  
  584.